﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace zmMoniter
{
    public delegate void ClientConnectionFalseHandler(object sender, LoseConnectFalseEventArgs e);
    public delegate void ClientReceiveMessageHander(object sender, ClientReceiveMesageEventArgs e);
    public delegate void ClientReceiveFileHandler(object sender, ClientReceiveFileEventArgs e);

    /// <summary>
    /// TcpUtil的客户端组件类
    /// 提供简单的Tcp通信功能
    /// </summary>
    public class TcpUtilClient
    {

        #region --事件--

        public event ClientConnectionFalseHandler loseConnected;
        public event ClientReceiveMessageHander receivMessage;
        public event ClientReceiveFileHandler receiveFile;

        #endregion

        #region  --本地私有变量--

        private IPEndPoint serverIPEndPoint;
        private IPEndPoint localIPEndPoint;
        private Boolean logEnable;
        private Int32 timeOut = 30000;
        private TcpClient myClient;
        private NetworkStream ns;
        private const Int32 BUFFERSIZE = 4096;
        private String logPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "CommunicateLog.txt";
        MemoryStream memStream = new MemoryStream();

        Thread checkSendThread;
        Thread checkTimeThread;

        private DateTime lastCheckTime;

        #endregion

        #region --属性--

        /// <summary>
        /// 服务器终结点
        /// </summary>
        public IPEndPoint ServerIPEndPoint
        {
            get { return serverIPEndPoint; }
            set { serverIPEndPoint = value; }
        }

        /// <summary>
        /// 本地终结点
        /// </summary>
        public IPEndPoint LocalIPEndPoint
        {
            get { return localIPEndPoint; }
            set { localIPEndPoint = value; }
        }

        /// <summary>
        /// 是否开启日志
        /// </summary>        
        public Boolean LogEnable
        {
            get { return logEnable; }
            set { logEnable = value; }
        }

        /// <summary>
        /// 判断掉线时间
        /// </summary>
        public Int32 TimeOut
        {
            get { return timeOut; }
            set { timeOut = value; }
        }

        #endregion

        #region --外部方法--

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="localIPEndPoint">本地终结点</param>
        /// <param name="logEnable">是否开启日志</param>
        /// <param name="timeOut">判断是否与服务器断开连接的时间</param>
        public TcpUtilClient(IPEndPoint serverIPEndPoint, Boolean logEnable, Int32 timeOut)
        {
            this.serverIPEndPoint = serverIPEndPoint;
            this.logEnable = logEnable;
            this.timeOut = timeOut;
        }

        /// <summary>
        /// 与服务器连接的方法
        /// </summary>
        public Int32 Connect()
        {
            int result = 0;
            try
            {
                myClient = new TcpClient();
                myClient.Connect(this.serverIPEndPoint);
                ns = myClient.GetStream();
                this.localIPEndPoint = (IPEndPoint)myClient.Client.LocalEndPoint;

                ////启动向服务器发送校验信息线程
                //checkSendThread = new Thread(new ThreadStart(CheckMesg));
                //checkSendThread.IsBackground = true;
                //checkSendThread.Start();
                //启动校验时间线程
                //checkTimeThread = new Thread(new ThreadStart(CheckTime));
                //checkTimeThread.IsBackground = true;
                //checkTimeThread.Start();

                result = 1;
                this.WriteLog(this.logEnable, this.logPath, "连接服务器" + this.serverIPEndPoint + "成功! 时间:" + DateTime.Now);

                this.WaitData();
            }
            catch (Exception ex)
            {
                result = 0;
                this.WriteLog(this.logEnable, this.logPath, "连接服务器" + this.serverIPEndPoint + "失败! 原因:" + ex.Message + "  时间:" + DateTime.Now);
            }
            return result;
        }

        /// <summary>
        /// 往服务器发送消息
        /// </summary>
        /// <param name="buffer">发送的字节数组</param>
        public Int32 SendMesg(byte[] buffer)
        {
            Int32 result = 0;
            try
            {
                //byte[] packHeadBytes = Encoding.ASCII.GetBytes("($SAT:$)");
                //byte[] packEndBytes = Encoding.ASCII.GetBytes("($:END$)");

                //byte[] requestBytes = new byte[buffer.Length + packHeadBytes.Length + packEndBytes.Length];

                //Array.Copy(packHeadBytes,0,requestBytes,0,packHeadBytes.Length);
                //Array.Copy(buffer, 0, requestBytes, packHeadBytes.Length, buffer.Length);
                //Array.Copy(packEndBytes, 0, requestBytes, packHeadBytes.Length + buffer.Length, packEndBytes.Length);

                ns.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(SendCallback), ns);

                result = 1;
            }
            catch (Exception ex)
            {
                result = 0;
                //写入日志
                this.WriteLog(this.logEnable, this.logPath, "连接服务器" + this.serverIPEndPoint + "失败! 原因:" + ex.Message + "  时间:" + DateTime.Now);
            }
            return result;
        }

        /// <summary>
        /// 往服务器端发送文件
        /// </summary>
        /// <param name="path">文件所在的路径</param>
        /// <returns></returns>
        public Int32 SendFile(String path)
        {
            Int32 result = 0;
            try
            {
                FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
                byte[] filebytes = new byte[stream.Length];
                stream.Read(filebytes, 0, (int)stream.Length);
                stream.Close();

                String fileName = path.Substring(path.LastIndexOf(@"\") + 1);

                byte[] packHeadBytes = Encoding.ASCII.GetBytes("($SAT:$)");
                byte[] fileNameBytes = Encoding.UTF8.GetBytes("($:&FILENAME&" + fileName + "&$)");
                byte[] packEndBytes = Encoding.ASCII.GetBytes("($:END$)");

                byte[] requestBytes = new byte[filebytes.Length + packHeadBytes.Length + fileNameBytes.Length + packEndBytes.Length];

                Array.Copy(packHeadBytes, 0, requestBytes, 0, packHeadBytes.Length);
                Array.Copy(fileNameBytes, 0, requestBytes, packHeadBytes.Length, fileNameBytes.Length);
                Array.Copy(filebytes, 0, requestBytes, packHeadBytes.Length + fileNameBytes.Length, filebytes.Length);
                Array.Copy(packEndBytes, 0, requestBytes, packHeadBytes.Length + fileNameBytes.Length + filebytes.Length, packEndBytes.Length);

                ns.BeginWrite(requestBytes, 0, requestBytes.Length, new AsyncCallback(SendCallback), ns);

                result = 1;

            }
            catch (Exception ex)
            {
                result = 0;
                this.WriteLog(this.logEnable, this.logPath, "向服务器发送文件出现异常:" + ex.Message + "  时间:" + DateTime.Now);
            }
            return result;
        }

        /// <summary>
        /// 保存文件方法
        /// </summary>
        /// <param name="buffer">文件的字节数组</param>
        /// <param name="path">要保存文件的路径</param>
        /// <returns>返回结果 1 成功, 2 失败</returns>
        public Int32 SaveFile(byte[] buffer, String path)
        {
            Int32 result = 0;
            try
            {
                //int temp = path.LastIndexOf(@"\");
                String directory = path.Substring(0, path.LastIndexOf(@"\"));
                if (Directory.Exists(directory) == false)
                {
                    Directory.CreateDirectory(directory);
                }

                FileInfo fileInfo = new FileInfo(path);
                FileStream fs = fileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                fs.Write(buffer, 0, buffer.Length);
                fs.Close();

                if (fileInfo.Exists)
                {
                    result = 1;
                }
            }
            catch (Exception ex)
            {
                result = 0;
                throw;
            }
            return result;
        }

        /// <summary>
        /// 断开与服务器的连接
        /// </summary>
        public Int32 Close()
        {
            Int32 result = 0;
            try
            {
                myClient.Client.Close();

                result = 1;

                this.WriteLog(this.logEnable, this.logPath, "与服务器断开连接.   时间:" + DateTime.Now);
            }
            catch (Exception ex)
            {
                result = 0;

                this.WriteLog(this.logEnable, this.logPath, "关闭连接时发生异常! 可能是没有与服务器连接.   异常信息:" + ex.Message);
            }
            return result;
        }

        #endregion

        #region --内部方法--

        /// <summary>
        /// 等待客户端发送数据
        /// </summary>
        private void WaitData()
        {
            try
            {
                byte[] buffer = new byte[BUFFERSIZE];
                ns.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallBack), buffer);
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "等待数据时发生未知异常:" + ex.ToString());
            }
        }

        /// <summary>
        /// 等到数据的回调函数
        /// </summary>
        /// <param name="ar">等到数据的回调对象</param>
        private void ReadCallBack(IAsyncResult ar)
        {
            try
            {
                byte[] buffer = (byte[])ar.AsyncState;
                int count = ns.EndRead(ar);
                this.memStream.Write(buffer, 0, count);
                byte[] memBytes = this.memStream.ToArray();
                this.memStream = new MemoryStream();  //清理内存
                if (this.receivMessage != null)
                {
                    ClientReceiveMesageEventArgs e = new ClientReceiveMesageEventArgs();
                    e.ReceiveTime = DateTime.Now;
                    e.Buffer = memBytes;
                    this.receivMessage(this, e);
                }
                //this.memStream = new MemoryStream();  //清理内存
                this.WaitData();
            }
            catch (IOException ex)
            {
                myClient.Client.Close();
                //服务器进程关闭!
                if (this.loseConnected != null)
                {
                    LoseConnectFalseEventArgs e = new LoseConnectFalseEventArgs();
                    e.Reason = "与服务器失去联系!";
                    e.FaultTime = DateTime.Now;
                    this.loseConnected(this, e);
                }
                //try
                //{
                //    checkTimeThread.Abort();
                //    checkSendThread.Abort();
                //}
                //catch (Exception exAbort)
                //{
                //    this.WriteLog(this.logEnable, this.logPath, "退出检查线程时 发生异常:" + exAbort.ToString());
                //}
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:" + ex.ToString());
                this.WaitData();
            }
        }

        /// <summary>
        /// 发送数据回调会点方法
        /// </summary>
        /// <param name="ar"></param>
        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                ns.EndWrite(ar);
            }
            catch (Exception ex)
            {
                this.WriteLog(this.logEnable, this.logPath, "发生未知异常:" + ex.ToString());
            }
        }

        /// <summary>
        /// 写入日志方法
        /// </summary>
        /// <param name="logEnable">是否写入日志</param>
        /// <param name="logPath">日志路径</param>
        /// <param name="content">日志内容</param>
        private void WriteLog(Boolean logEnable, String logPath, String content)
        {
            if (logEnable == true)
            {
                if (logEnable == true)
                {
                    try
                    {
                        StreamWriter sw = File.AppendText(logPath);
                        lock (sw)
                        {
                            try
                            {
                                sw.WriteLine(content);
                            }
                            catch
                            { }
                            finally
                            {
                                sw.Close();
                            }
                        }
                    }
                    catch (Exception)
                    {
                        //该异常是因为多个线程同时,写日志造成的.
                    }
                }
            }
        }

        /// <summary>
        /// 定时向服务器端发送校验数据
        /// </summary>
        private void CheckMesg()
        {
            while (true)
            {
                try
                {
                    byte[] checkSendBytes = Encoding.ASCII.GetBytes("($CONNECT$)");
                    ns.Write(checkSendBytes, 0, checkSendBytes.Length);
                    Thread.Sleep(this.timeOut / 2); //在判断是否掉线时间的一半时间校验一次
                }
                catch//如果Client关闭连接发送数据会发生异常
                {
                    //checkSendThread.Abort();
                    //break;
                }
            }
        }

        /// <summary>
        /// 判断是否掉线的方法
        /// </summary>
        private void CheckTime()
        {
            Boolean isExit = false;

            while (isExit == false)
            {
                Thread.Sleep(this.timeOut / 2); //在判断是否掉线时间的一半时间校验一次
                TimeSpan span = DateTime.Now - this.lastCheckTime;
                if (span.TotalMilliseconds > this.timeOut)//在指定的时间没有返回信息.
                {
                    myClient.Client.Close();
                    if (this.loseConnected != null)
                    {
                        LoseConnectFalseEventArgs e = new LoseConnectFalseEventArgs();
                        e.Reason = "服务器在指定的时间,没有返回信息";
                        e.FaultTime = DateTime.Now;

                        this.loseConnected(this, e);
                    }

                    //isExit = true; //退出循环
                }
            }
        }
        #endregion

    }
}
